﻿using System;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using System.Web.Mvc;
using System.Web.Routing;

namespace Entropy.Ext
{
	public class ColumnFluent<TModel>
	{
		private readonly List<MemberInfo> members;
		private readonly List<IColumn> columns;

		public ColumnFluent(List<MemberInfo> members, List<IColumn> columns)
		{
			this.members = members;
			this.columns = columns;
		}


		public static implicit operator IColumn[](ColumnFluent<TModel> model)
		{
			return model.columns.ToArray();
		}

		private static MemberInfo ExtractMember(Expression expr)
		{
			if (expr.NodeType != ExpressionType.Assign)
				throw new Exception("Assign expected but found:" + expr.NodeType);
			
			var bin = (BinaryExpression)expr;
			
			var leftMember = ((MemberExpression)bin.Left).Member;
			var rightMember = ((MemberExpression)bin.Right).Member;
			return rightMember;
		}

		private IColumn FindColumn(Expression expr)
		{
			int index = members.FindIndex(m => m == ExtractMember(expr));
			if (index < 0)
				throw new Exception("Unexpected member: " + expr);
			return columns[index];
		}

		public ColumnFluent<TModel> ForMember(Expression<Func<TModel, bool>> member, Action<IBooleanColumn> configure)
		{
			configure((IBooleanColumn) FindColumn(member));
			return this;
		}

		public ColumnFluent<TModel> ForMember(Expression<Func<TModel, int>> member, Action<INumberColumn> configure)
		{
			configure((INumberColumn) FindColumn(member));
			return this;
		}
	}

	/// <summary>
	/// Feature!
	/// When we provide ajax data for the ExtJs DataStore, we would like to also provide totalCount, message etc.
	/// Lets encapsulate it inside predefined kind of response and autowrap on client side
	/// </summary>
	public static class StoreExtJsExtensions 
	{
		private static IAjaxProxy UrlFromRouteValues(IExtFactory ext, RouteValueDictionary routeValues, IDataReader reader, bool includeParameters)
		{
			var page = ExtFactoryProxy.PageByExt(ext);
			var url = ActionLinkExtension.StringifyActionUrl(page.Html, routeValues, includeParameters);
			return  ext.CreateAjaxProxy(url: url, reader: reader);
		}

		public static IStore ServerStoreWithoutModel<TController>(this IExtFactory ext, Expression<Action<TController>> action) where TController : Controller
		{
			var reader = ext.JsonReader(root: ExtStoreViewModel.RootProperty); 
			var proxy = UrlFromRouteValues(ext, ActionLinkExtension.GetRouteValuesFromExpression(action), reader, false);
			return ext.CreateDataStore(autoLoad: false, proxy: proxy);
		}

		public static ColumnFluent<TModel> ServerColumns<TModel>(this IExtFactory ext)
		{
			var members = new List<MemberInfo>();
			var columns = new List<IColumn>();

			foreach (var prop in typeof (TModel).GetProperties())
			{
				members.Add(prop);
				string header;
				GetColumnAttributes(prop, out header);
				switch (ResolveFieldType(prop))
				{
					case FieldType.Float:
                        columns.Add(ext.NumberColumn(
                            dataIndex: prop.Name,
                            text: header,
                            sortable: true,
                            align: ColumnAlign.Right
                        ));
				        break;
				    case FieldType.Int:
						columns.Add(ext.NumberColumn(
							dataIndex: prop.Name,
							text: header,
							sortable: true,
                            format: "0",
                            align: ColumnAlign.Right
						));
						break;

					case FieldType.Date:
						//var dateOnly = prop.GetCustomAttribute<DateOnlyAttribute>() != null;

						columns.Add(ext.DateColumn(
							dataIndex: prop.Name,
							text: header,
							sortable: true
//							renderer: dateOnly ? ext.DateRenderer("m-d-Y") : null
						));
						break;

					case FieldType.Boolean:
						columns.Add(ext.BooleanColumn(
							dataIndex: prop.Name,
							text: header,
							sortable: true
						));
						break;

					default:
						columns.Add(ext.Column(
							dataIndex: prop.Name,
							text: header,
							sortable: true
						));
						break;
				}
			}

			return new ColumnFluent<TModel>(members, columns);
		}

		private static void GetColumnAttributes(PropertyInfo prop, out string header)
		{
			header = null;
			var attr = prop.GetCustomAttribute<DisplayAttribute>();
			if (attr != null)
			{
				header = attr.Name;
			}

			if (string.IsNullOrEmpty(header))
				header = prop.Name;
		}

		public static IStore ServerStore<TController, TModel>(this IExtFactory ext, Expression<Func<TController, StoreResult<TModel>>> action, bool includeParameters = false) where TController : Controller
		{
			var reader = ext.JsonReader(root: ExtStoreViewModel.RootProperty); 
			var proxy = UrlFromRouteValues(ext, ActionLinkExtension.GetRouteValuesFromExpression(action), reader, includeParameters);
			return
				ext.CreateDataStore(
					autoLoad: true,
				    proxy: proxy,
//					buffered: true,
//					pageSize: 25,
					fields: ResolveFields(ext, typeof(TModel))
				);
		}

		public static IStore ServerStore(this IExtFactory ext, string url, Type modelType, bool includeParameters = false) 
		{
			var reader = ext.JsonReader(root: ExtStoreViewModel.RootProperty);
            var proxy = ext.CreateAjaxProxy(url: url, reader: reader);
			return
				ext.CreateDataStore(
					autoLoad: true,
				    proxy: proxy,
//					buffered: true,
//					pageSize: 25,
					fields: ResolveFields(ext, modelType)
				);
		}

		private static IDataField[] ResolveFields(IExtFactory ext, Type type)
		{
			var result = new List<IDataField>();
			foreach (var prop in type.GetProperties())
			{
				Debug.Assert(prop != null);
				FieldType fieldType = ResolveFieldType(prop);
				result.Add(ext.DataField(name: prop.Name, type: fieldType)); // todo dateFormat etc
			}
			return result.ToArray();
		}

		private static FieldType ResolveFieldType(PropertyInfo prop)
		{
			FieldType fieldType;
			var type = prop.PropertyType;
			bool nullable = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
			if (nullable)
				type = type.GetGenericArguments()[0];

			var propertyType = type;
			if (propertyType == typeof (int) ||
			    propertyType == typeof (byte) ||
			    propertyType == typeof (long) ||
			    propertyType == typeof (short))
				fieldType = FieldType.Int;
			else if (propertyType == typeof (bool))
				fieldType = FieldType.Boolean;
			else if (propertyType == typeof (DateTime))
				fieldType = FieldType.Date;
			else if (propertyType == typeof (double) || propertyType == typeof (decimal) || propertyType == typeof (float))
				fieldType = FieldType.Float;
			else if (propertyType == typeof (string))
				fieldType = FieldType.String;
			else
				fieldType = FieldType.String;
			return fieldType;
		}
	}
}